/*
* Copyright (c) 2003, the JUNG Project and the Regents of the University 
* of California
* All rights reserved.
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* http://jung.sourceforge.net/license.txt for a description.
*/
package edu.uci.ics.jung.algorithms.generators.random;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.apache.commons.collections15.Factory;

import edu.uci.ics.jung.algorithms.generators.GraphGenerator;
import edu.uci.ics.jung.graph.Graph;

/**
 * Graph generator that generates undirected graphs with power-law degree
 * distributions.
 * 
 * @author Scott White
 * @see "A Steady State Model for Graph Power Law by David Eppstein and Joseph Wang"
 */
public class EppsteinPowerLawGenerator<V, E> implements GraphGenerator<V, E> {
	private int mNumVertices;
	private int mNumEdges;
	private int mNumIterations;
	private double mMaxDegree;
	private Random mRandom;
	private Factory<Graph<V, E>> graphFactory;
	private Factory<V> vertexFactory;
	private Factory<E> edgeFactory;

	/**
	 * Creates an instance with the specified factories and specifications.
	 * 
	 * @param graphFactory
	 *            the factory to use to generate the graph
	 * @param vertexFactory
	 *            the factory to use to create vertices
	 * @param edgeFactory
	 *            the factory to use to create edges
	 * @param numVertices
	 *            the number of vertices for the generated graph
	 * @param numEdges
	 *            the number of edges the generated graph will have, should be
	 *            Theta(numVertices)
	 * @param r
	 *            the number of iterations to use; the larger the value the
	 *            better the graph's degree distribution will approximate a
	 *            power-law
	 */
	public EppsteinPowerLawGenerator(Factory<Graph<V, E>> graphFactory,
			Factory<V> vertexFactory, Factory<E> edgeFactory, int numVertices,
			int numEdges, int r) {
		this.graphFactory = graphFactory;
		this.vertexFactory = vertexFactory;
		this.edgeFactory = edgeFactory;
		mNumVertices = numVertices;
		mNumEdges = numEdges;
		mNumIterations = r;
		mRandom = new Random();
	}

	protected Graph<V, E> initializeGraph() {
		Graph<V, E> graph = null;
		graph = graphFactory.create();
		for (int i = 0; i < mNumVertices; i++) {
			graph.addVertex(vertexFactory.create());
		}
		List<V> vertices = new ArrayList<V>(graph.getVertices());
		while (graph.getEdgeCount() < mNumEdges) {
			V u = vertices.get((int) (mRandom.nextDouble() * mNumVertices));
			V v = vertices.get((int) (mRandom.nextDouble() * mNumVertices));
			if (!graph.isSuccessor(v, u)) {
				graph.addEdge(edgeFactory.create(), u, v);
			}
		}

		double maxDegree = 0;
		for (V v : graph.getVertices()) {
			maxDegree = Math.max(graph.degree(v), maxDegree);
		}
		mMaxDegree = maxDegree; // (maxDegree+1)*(maxDegree)/2;

		return graph;
	}

	/**
	 * Generates a graph whose degree distribution approximates a power-law.
	 * 
	 * @return the generated graph
	 */
	@Override
	public Graph<V, E> create() {
		Graph<V, E> graph = initializeGraph();

		List<V> vertices = new ArrayList<V>(graph.getVertices());
		for (int rIdx = 0; rIdx < mNumIterations; rIdx++) {

			V v = null;
			int degree = 0;
			do {
				v = vertices.get((int) (mRandom.nextDouble() * mNumVertices));
				degree = graph.degree(v);

			} while (degree == 0);

			List<E> edges = new ArrayList<E>(graph.getIncidentEdges(v));
			E randomExistingEdge = edges
					.get((int) (mRandom.nextDouble() * degree));

			// FIXME: look at email thread on a more efficient RNG for arbitrary
			// distributions

			V x = vertices.get((int) (mRandom.nextDouble() * mNumVertices));
			V y = null;
			do {
				y = vertices.get((int) (mRandom.nextDouble() * mNumVertices));

			} while (mRandom
					.nextDouble() > ((graph.degree(y) + 1) / mMaxDegree));

			if (!graph.isSuccessor(y, x) && x != y) {
				graph.removeEdge(randomExistingEdge);
				graph.addEdge(edgeFactory.create(), x, y);
			}
		}

		return graph;
	}

	/**
	 * Sets the seed for the random number generator.
	 * 
	 * @param seed
	 *            input to the random number generator.
	 */
	public void setSeed(long seed) {
		mRandom.setSeed(seed);
	}
}
